package com.secure.advice;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.secure.bean.SecureRequest;
import com.secure.constant.RequestConstant;
import com.secure.handler.SecureRequestHandler;
import com.secure.util.RequestUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;

/**
 * 请求参数解密增强
 *
 * @author duchao
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class RequestParameterSecureAdvice {

    private final ObjectMapper objectMapper;
    private final SecureRequestHandler secureRequestHandler;

    @Around("ApiPointcut.api()")
    public Object requestPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        if (secureRequestHandler.isNeedSecureRequestForParameter(methodSignature.getMethod())) {
            SecureRequest secureRequest = wrapSecureRequest(RequestUtil.getCurrentRequest());
            String plainData = secureRequestHandler.decryptAndVerifySignatureRequest(secureRequest);
            String[] parameterNames = methodSignature.getParameterNames();
            Object[] parameterValues = joinPoint.getArgs();
            Class<?>[] parameterTypes = methodSignature.getParameterTypes();
            fillRequestParameter(plainData, parameterNames, parameterValues, parameterTypes);
            return joinPoint.proceed(parameterValues);
        }
        return joinPoint.proceed();
    }

    /**
     * 安全请求参数封装
     *
     * @param request 请求对象
     * @throws IOException
     */
    private SecureRequest wrapSecureRequest(HttpServletRequest request) throws IOException {
        Enumeration<String> parameterNames = request.getParameterNames();
        if (parameterNames.hasMoreElements()) {
            String userId = request.getParameter(RequestConstant.USERID);
            String appId = request.getParameter(RequestConstant.APPID);
            String timestamp = request.getParameter(RequestConstant.TIMESTAMP);
            String data = request.getParameter(RequestConstant.DATA);
            boolean isNeedUrlDecode = isNeedUrlDecodeForParameter(request);
            SecureRequest secureRequest = new SecureRequest();
            secureRequest.setUserId(isNeedUrlDecode ? URLDecoder.decode(userId, StandardCharsets.UTF_8.toString()) : userId);
            secureRequest.setAppId(isNeedUrlDecode ? URLDecoder.decode(appId, StandardCharsets.UTF_8.toString()) : appId);
            secureRequest.setTimestamp(StrUtil.isNotBlank(timestamp) ? Long.valueOf(timestamp) : null);
            secureRequest.setData(isNeedUrlDecode ? URLDecoder.decode(data, StandardCharsets.UTF_8.toString()) : data);
            return secureRequest;
        }
        return null;
    }

    /**
     * 判断是否需要请求参数解码
     * 服务消费方调用告知需要请求解码（特殊情况）
     *
     * @param request 请求
     * @return
     */
    private boolean isNeedUrlDecodeForParameter(HttpServletRequest request) {
        String decodeRequest = request.getHeader(RequestConstant.DECODE_REQUEST);
        return "true".equals(decodeRequest);
    }

    /**
     * 填充请求参数
     *
     * @param plainData       明文字符串
     * @param parameterNames  参数名
     * @param parameterValues 入参值
     * @throws JsonProcessingException
     */
    private void fillRequestParameter(String plainData, String[] parameterNames, Object[] parameterValues, Class<?>[] parameterTypes) throws JsonProcessingException {
        Map<String, Object> plainMap = objectMapper.readValue(plainData, new TypeReference<Map<String, Object>>() {
        });
        for (int i = 0; i < parameterNames.length; i++) {
            Object parameterValue = plainMap.get(parameterNames[i]);
            parameterValues[i] = objectMapper.readValue(objectMapper.writeValueAsString(parameterValue), parameterTypes[i]);
        }
    }

}
